1 module hip.api.data.font; 2 import hip.api.data.asset; 3 public import hip.api.renderer.texture; 4 5 6 alias HipCharKerning = int[dchar]; 7 alias HipFontKerning = HipCharKerning[dchar]; 8 ///see hip.graphics.g2d.textrenderer 9 struct HipTextRendererVertexAPI 10 { 11 float[3] vPosition = [0,0,0]; 12 float[2] vTexST = [0,0]; 13 } 14 15 /** 16 * A text information that is returned from the word wrap range. 17 * Beyond the information with line and width, it also has a cache. 18 * This cache is used for optimization in both kerning and associative array 19 * lookup. This can make a big difference by having a single lookup instead of 20 * 2. The lookup is the slowest part of text rendering, which makes this a lot faster. 21 */ 22 pragma(LDC_no_typeinfo) 23 struct HipLineInfo 24 { 25 string line; 26 int height; 27 int width; 28 int minYOffset; 29 const(HipFontChar)*[512] fontCharCache = void; 30 int[512] kerningCache = void; 31 } 32 33 pragma(LDC_no_typeinfo) 34 struct HipWordWrapRange 35 { 36 private string inputText; 37 private IHipFont font; 38 private int maxWidth, currIndex; 39 private HipLineInfo currLine = void; 40 private bool hasFinished; 41 42 void initialize(string inputText, IHipFont font, int maxWidth) @nogc 43 { 44 this.inputText = inputText; 45 currLine.height = 0; 46 currLine.width = 0; 47 currLine.minYOffset = 0; 48 this.font = font; 49 this.maxWidth = maxWidth <= 0 ? int.max : maxWidth; 50 currIndex = 0; 51 hasFinished = false; 52 } 53 54 bool empty() @nogc {return hasFinished;} 55 /** 56 * Every time it pops the front, it will search for words. Words are defined as text delimited 57 * by spaces. If a word is bigger than the max width, it will be cutten and the word will spam 58 * through multiple lines. 59 */ 60 void popFront() @nogc 61 { 62 const(HipFontChar)* ch, next; 63 int currWidth = 0, wordWidth = 0, wordStartIndex = currIndex; 64 uint spaceWidth = font.spaceWidth; 65 66 for(int i = currIndex, it = 0; i < inputText.length; i++, it++) 67 { 68 if(ch is null) 69 { 70 ch = inputText[i] in font.characters; 71 if(ch is null) 72 { 73 currLine.kerningCache[it] = 0; 74 currLine.fontCharCache[it] = null; 75 continue; 76 } 77 } 78 currLine.height = ch.height > currLine.height ? ch.height : currLine.height; 79 currLine.minYOffset = ch.yoffset < currLine.minYOffset ? ch.yoffset : currLine.minYOffset; 80 int kern = 0; 81 if(i + 1 < inputText.length) 82 { 83 next = inputText[i+1] in font.characters; 84 if(next) kern = font.getKerning(ch, next); 85 } 86 currLine.kerningCache[it] = kern; 87 currLine.fontCharCache[it] = ch; 88 switch(inputText[i]) 89 { 90 case '\n': 91 currLine.line = inputText[currIndex..i]; 92 currLine.width = currWidth + wordWidth; 93 wordWidth = 0; 94 wordStartIndex = i+1; 95 currIndex = i+1; 96 return; 97 case ' ': 98 if(spaceWidth + wordWidth + currWidth > maxWidth) 99 { 100 currLine.line = inputText[currIndex..i]; 101 currLine.width = currWidth+wordWidth; 102 currIndex = i+1; 103 return; 104 } 105 else 106 { 107 currWidth+= wordWidth + spaceWidth; 108 wordStartIndex = i; 109 wordWidth = 0; 110 } 111 break; 112 default: 113 if(wordWidth + ch.xadvance + kern + currWidth > maxWidth) 114 { 115 if(wordStartIndex == currIndex) 116 { 117 currWidth = wordWidth; 118 wordStartIndex = i; 119 } 120 ///Subtract one for ignoring the space. 121 currLine.line = inputText[currIndex..wordStartIndex]; 122 currLine.width = currWidth; 123 if(wordStartIndex < inputText.length && inputText[wordStartIndex] == ' ') 124 wordStartIndex++; 125 currIndex = wordStartIndex; 126 return; 127 } 128 wordWidth += ch.xadvance + kern; 129 break; 130 } 131 ch = next; 132 } 133 if(currIndex < inputText.length && inputText[currIndex] == ' ') 134 currIndex++; 135 currLine.line = inputText[currIndex..$]; 136 currLine.width = currWidth+wordWidth; 137 currIndex = cast(int)inputText.length; 138 if(currLine.line.length == 0) hasFinished = true; 139 } 140 141 HipLineInfo front() @nogc 142 { 143 if(currIndex == 0) popFront(); 144 return currLine; 145 } 146 } 147 148 struct HipFontChar 149 { 150 uint id; 151 ///Those are in absolute values 152 int x, y, width, height; 153 154 int xoffset, yoffset, xadvance, page, chnl; 155 156 ///Normalized values 157 float normalizedX, normalizedY, normalizedWidth, normalizedHeight; 158 int glyphIndex; 159 void putCharacterQuad(float x, float y, float depth, HipTextRendererVertexAPI[] quad, float scale = 1) const @nogc 160 { 161 import hip.util.data_structures; 162 float w = width*scale, h = height*scale; 163 //Gen vertices 164 quad[0..4] = [ 165 //Top left 166 HipTextRendererVertexAPI( 167 [x, y, depth], 168 [normalizedX, normalizedY] //ST 169 ), 170 //Top Right 171 HipTextRendererVertexAPI( 172 [x+w, y,depth], 173 [normalizedX + normalizedWidth, normalizedY] //S + Wnorm, T 174 ), 175 //Bot right 176 HipTextRendererVertexAPI( 177 [x+ w, y +h, depth], 178 [ 179 normalizedX + normalizedWidth, //S+Wnorm 180 normalizedY + normalizedHeight //T+Hnorm 181 ] 182 ), 183 //Bot left 184 HipTextRendererVertexAPI( 185 [x, y + h, depth], 186 [normalizedX, normalizedY + normalizedHeight] // S, T+Hnorm 187 ) 188 ].staticArray; 189 190 } 191 } 192 193 interface IHipFont 194 { 195 int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const @nogc; 196 int getKerning(dchar current, dchar next) const @nogc; 197 uint getHeight() const @nogc; 198 /** 199 * 200 * Params: 201 * text = The text 202 * linesWidths = Save width per line 203 * biggestWidth = The biggest width in lines 204 * height = Height of all the lines together 205 * maxWidth = If maxWidth != -1, it will break the text into lines automatically. 206 */ 207 void calculateTextBounds(in string text, ref uint[] linesWidths, out int biggestWidth, out int height, int maxWidth = -1) const; 208 /** 209 * 210 * Params: 211 * text = Input text 212 * Returns: 0 if there is no line break is being done 213 */ 214 final uint getTextHeight(in string text) 215 { 216 import hip.util.string; 217 return count(text, "\n") * lineBreakHeight; 218 } 219 HipWordWrapRange wordWrapRange(string text, int maxWidth) const @nogc; 220 ref HipFontChar[dchar] characters() @nogc; 221 ref inout(IHipTexture) texture() inout @nogc; 222 uint spaceWidth() const @nogc; 223 uint spaceWidth(uint newWidth) @nogc; 224 225 ///Used for reference as height for text 226 uint lineBreakHeight() const @nogc; 227 uint lineBreakHeight(uint newHeight) @nogc; 228 229 } 230 231 abstract class HipFont : HipAsset, IHipFont 232 { 233 234 abstract int getKerning(dchar current, dchar next) const; 235 abstract int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const; 236 237 this() 238 { 239 super("Font"); 240 } 241 242 ///Underlying GPU texture 243 IHipTexture _texture; 244 HipFontChar[dchar] _characters; 245 ///Saves the space width for the bitmap text process the ' '. If the original spaceWidth is == 0, it won't draw a quad 246 uint _spaceWidth; 247 ///How much the line break will offset in Y the next char 248 uint _lineBreakHeight; 249 250 ///////Properties/////// 251 final ref HipFontChar[dchar] characters(){return _characters;} 252 final ref const(HipFontChar[dchar]) characters() const {return _characters;} 253 final ref inout(IHipTexture) texture() inout {return _texture;} 254 final uint spaceWidth() const {return _spaceWidth;} 255 final uint spaceWidth(uint newWidth){return _spaceWidth = newWidth;} 256 final uint lineBreakHeight() const {return _lineBreakHeight;} 257 final uint lineBreakHeight(uint newHeight){return _lineBreakHeight = newHeight;} 258 259 260 abstract uint getHeight() const; 261 262 final HipWordWrapRange wordWrapRange(string text, int maxWidth) const @nogc 263 { 264 ///Needs to be returned like that or else, it will memset everytime 265 HipWordWrapRange ret = void; 266 ret.initialize(text, cast(IHipFont)this, maxWidth); 267 return ret; 268 } 269 270 271 final void calculateTextBounds(string text, ref uint[] linesWidths, out int biggestWidth, out int height, int maxWidth = -1) const 272 { 273 int i = 0; 274 foreach(HipLineInfo lineInfo; wordWrapRange(text, maxWidth)) 275 { 276 if(lineInfo.width > biggestWidth) biggestWidth = lineInfo.width; 277 if(linesWidths.length < i+1) 278 linesWidths.length++; 279 linesWidths[i++] = lineInfo.width; 280 } 281 height = lineBreakHeight*i; 282 } 283 HipFont getFontWithSize(uint size); 284 285 override void onFinishLoading(){} 286 override void onDispose(){} 287 override bool isReady() const {return _texture !is null;} 288 289 }